﻿using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace Kyoh.Lib.WinForms
{
    /// <summary>ラベル表示付きの <see cref="TrackBar"/>。</summary>
    public partial class LabeledTrackBar : UserControl
    {
        /// <summary><see cref="LabeledTrackBar"/> クラスの新しいインスタンスを初期化します。</summary>
        public LabeledTrackBar()
        {
            InitializeComponent();
            this.LayoutTable();
        }

        /// <summary>コントロールに描画された目盛り間のデルタを指定する値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(1)]
        public int TickFrequency { get { return this.trackBar.TickFrequency; } set { this.trackBar.TickFrequency = value; } }

        /// <summary>トラック バーにおけるスクロール ボックスの現在位置を表す数値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(0)]
        public int Value
        {
            get { return this.trackBar.Value; }
            set
            {
                this.topValueLabel.Text = value.ToString();
                this.bottomValueLabel.Text = value.ToString();
                this.trackBar.Value = value;
            }
        }

        /// <summary>スクロール ボックスが大きく移動したときに System.Windows.Forms.TrackBar.Value プロパティに対して加算または減算される値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(5)]
        public int LargeChange { get { return this.trackBar.LargeChange; } set { this.trackBar.LargeChange = value; } }

        /// <summary>スクロール ボックスがわずかに移動したときに System.Windows.Forms.TrackBar.Value プロパティに対して加算または減算される値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(1)]
        public int SmallChange { get { return this.trackBar.SmallChange; } set { this.trackBar.SmallChange = value; } }

        /// <summary>この System.Windows.Forms.TrackBar が使用する範囲の上限値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [RefreshProperties(RefreshProperties.All)]
        [DefaultValue(10)]
        public int Maximum
        {
            get { return this.trackBar.Maximum; }
            set
            {
                this.trackBar.Maximum = value;
                this.Label();
            }
        }

        /// <summary>この System.Windows.Forms.TrackBar が使用する範囲の下限値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(0)]
        [RefreshProperties(RefreshProperties.All)]
        public int Minimum
        {
            get { return this.trackBar.Minimum; }
            set
            {
                this.trackBar.Minimum = value;
                this.Label();
            }
        }

        /// <summary>トラック バーが水平方向または垂直方向であることを示す値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(Orientation.Horizontal)]
        public Orientation Orientation
        {
            get { return this.trackBar.Orientation; }
            set
            {
                if (this.trackBar.Orientation == value)
                    return;
                this.trackBar.Orientation = value;
                this.LayoutTable();
            }
        }

        /// <summary><see cref="TrackBar"/> の内容を右から左方向にレイアウトするかどうかを示す値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(false)]
        public bool RightToLeftLayout
        {
            get { return this.trackBar.RightToLeftLayout; }
            set
            {
                if (this.trackBar.RightToLeftLayout == value)
                    return;
                this.trackBar.RightToLeftLayout = value;
                this.LayoutTable();
            }
        }

        /// <summary>トラック バー上に目盛りを表示する方法を示す値を取得または設定します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(TickStyle.BottomRight)]
        public TickStyle TickStyle
        {
            get { return this.trackBar.TickStyle; }
            set
            {
                if (this.trackBar.TickStyle == value)
                    return;
                this.trackBar.TickStyle = value;
                this.LayoutTable();
            }
        }

        private void Label()
        {
            this.leftMinLabel.Text =
            this.topMinLabel.Text =
            this.rightMinLabel.Text =
            this.bottomMinLabel.Text = this.Minimum.ToString();

            var middle = (this.Minimum + this.Maximum) / 2;
            this.leftMiddleLabel.Text =
            this.topCenterLabel.Text =
            this.rightMiddleLabel.Text =
            this.bottomCenterLabel.Text = middle.ToString();

            this.leftMaxLabel.Text =
            this.topMaxLabel.Text =
            this.rightMaxLabel.Text =
            this.bottomMaxLabel.Text = this.Maximum.ToString();
        }

        private void SwitchColumn(int index, bool visible)
        {
            if (!visible)
                this.tableLayoutPanel1.ColumnStyles[index].Width = 0;
            this.tableLayoutPanel1.ColumnStyles[index].SizeType = visible ? SizeType.AutoSize : SizeType.Absolute;
        }

        private void SwitchRow(int index, bool visible)
        {
            if (!visible)
                this.tableLayoutPanel1.RowStyles[index].Height = 0;
            this.tableLayoutPanel1.RowStyles[index].SizeType = visible ? SizeType.AutoSize : SizeType.Absolute;
        }

        private void SwitchColumnPercent(bool percent)
        {
            for (int i = 1; i <= 3; i++)
            {
                this.tableLayoutPanel1.ColumnStyles[i].Width = percent ? 33.33333f : (this.TickStyle == TickStyle.Both ? 14f : 10f);
                this.tableLayoutPanel1.ColumnStyles[i].SizeType = percent ? SizeType.Percent : SizeType.Absolute;
            }
        }

        private void SwitchRowPercent(bool percent)
        {
            for (int i = 2; i <= 4; i++)
            {
                this.tableLayoutPanel1.RowStyles[i].Height = percent ? 33.33333f : (this.TickStyle == TickStyle.Both ? 14f : 10f);
                this.tableLayoutPanel1.RowStyles[i].SizeType = percent ? SizeType.Percent : SizeType.Absolute;
            }
        }

        private void LayoutTable()
        {
            this.SuspendLayout();
            var topLeftLabelVisible = this.TickStyle == TickStyle.Both || this.TickStyle == TickStyle.TopLeft;
            var bottomRightLabelVisible = this.TickStyle == TickStyle.Both || this.TickStyle == TickStyle.BottomRight;

            this.SwitchColumn(0, this.Orientation == Orientation.Vertical && topLeftLabelVisible);
            this.SwitchColumnPercent(this.Orientation == Orientation.Horizontal);
            this.SwitchColumn(4, this.Orientation == Orientation.Vertical && bottomRightLabelVisible);

            this.SwitchRow(0, this.Orientation != Orientation.Horizontal || bottomRightLabelVisible);
            this.SwitchRow(1, this.Orientation == Orientation.Horizontal && topLeftLabelVisible);
            this.SwitchRowPercent(this.Orientation == Orientation.Vertical);
            this.SwitchRow(5, this.Orientation == Orientation.Horizontal && bottomRightLabelVisible);
            this.SwitchRow(6, this.Orientation == Orientation.Horizontal && this.TickStyle == TickStyle.TopLeft);
            this.ResumeLayout(true);
        }

        /// <summary>表示すべき領域の設定を行います。</summary>
        /// <param name="x">表示する画面左端からの位置。</param>
        /// <param name="y">表示する画面上端からの位置。</param>
        /// <param name="width">表示する幅。</param>
        /// <param name="height">表示する高さ。</param>
        /// <param name="specified"><paramref name="x"/>、<paramref name="y"/>、<paramref name="width"/>、<paramref name="height"/>の内、どの引数を有効な値として使用するかを表す <see cref="BoundsSpecified"/>。</param>
        protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
        {
            var topLeftLabelVisible = this.TickStyle == TickStyle.Both || this.TickStyle == TickStyle.TopLeft;
            var bottomRightLabelVisible = this.TickStyle == TickStyle.Both || this.TickStyle == TickStyle.BottomRight;

            int tracks = (this.TickStyle == TickStyle.Both ? 42 : 30);

            if (this.Orientation == Orientation.Horizontal)
            {
                height
                    = (bottomRightLabelVisible ? this.topValueLabel.PreferredHeight : 0)
                    + (topLeftLabelVisible ? this.topCenterLabel.PreferredHeight : 0)
                    + (bottomRightLabelVisible ? this.bottomCenterLabel.PreferredHeight : 0)
                    + (this.TickStyle == TickStyle.TopLeft ? this.bottomValueLabel.PreferredHeight : 0)
                    + tracks;
                specified |= BoundsSpecified.Height;
            }
            else
            {
                width
                    = (topLeftLabelVisible ? this.leftMiddleLabel.PreferredWidth : 0)
                    + (bottomRightLabelVisible ? this.rightMiddleLabel.PreferredWidth : 0)
                    + tracks;
                specified |= BoundsSpecified.Width;
            }
            base.SetBoundsCore(x, y, width, height, specified);
        }

        private void trackBar_ValueChanged(object sender, System.EventArgs e)
        {
            this.topValueLabel.Text =
            this.bottomValueLabel.Text = this.trackBar.Value.ToString();
        }

        /// <summary>トラックバーの値が変化した際に発生するイベント。</summary>
        public event EventHandler ValueChanged
        {
            add { this.trackBar.ValueChanged += value; }
            remove { this.trackBar.ValueChanged -= value; }
        }
    }
}